package com.wva.video.consumer.videoprocess;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.j256.simplemagic.ContentInfoUtil;
import com.wva.common.client.CensorClient;
import com.wva.common.enums.ObjectName;
import com.wva.ffmpeg.utils.*;
import com.wva.minio.utils.MinIOUtils;
import com.wva.video.constant.ProcessConst;
import com.wva.video.domain.dto.VideoProcessDTO;
import com.wva.video.domain.po.Video;
import com.wva.video.domain.po.VideoMetadata;
import com.wva.video.enums.QueueEnum;
import com.wva.video.enums.VideoStatus;
import com.wva.video.service.VideoMetadataService;
import com.wva.video.service.VideoService;
import io.minio.DownloadObjectArgs;
import io.minio.UploadObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;

/**
 * @Author: hong
 * @Date: 2025/2/17 15:33:39
 */
@Component
@RabbitListener(queues = "video.process.main")
@Slf4j
public class MainProcessQueueConsumer {
    @Autowired
    CensorClient censorClient;
    @Autowired
    VideoService videoService;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    MinIOUtils minIOUtils;
    @Autowired
    VideoMetadataService videoMetadataService;

    private Long videoId;
    private VideoProcessDTO videoProcessDTO;

    @RabbitHandler
    void handle(VideoProcessDTO videoProcessDTO) throws InterruptedException, JsonProcessingException {

        videoId = videoProcessDTO.getVideoId();
        this.videoProcessDTO = videoProcessDTO;
        String fileMd5 = videoProcessDTO.getFileMd5();
        log.info("视频{}: 转码、添加水印", videoId);

        //从minIO下载视频到本地服务器
        String tempDir = getTempDir();
        String inputVideoFilename = Paths.get(tempDir, "input_video").toString();
        downloadTempVideo(fileMd5, inputVideoFilename);

        //开始转码
        String outputVideoFilename = Paths.get(tempDir, "output_video.mp4").toString();
        VideoInfo videoInfo = processTempVideo(inputVideoFilename, outputVideoFilename);
        if (videoInfo == null) return;

        //上传转码后视频到minIO
        uploadProcessedVideo(outputVideoFilename);

        //上传封面
        if (ProcessConst.USE_FIRST_FRAME_AS_COVER) {
            uploadFirstFrameAsCover();
        }

        //新增视频原信息
        insertVideoMetaData(videoInfo);

        //决定下一步操作
        toNextStep();
    }

    private void uploadFirstFrameAsCover() {
        try {
            minIOUtils.getClient().uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(ObjectName.COVER.getBucket().getBucketName())
                            .object(ObjectName.COVER.format(videoId))
                            .filename(getCoverPath())
                            .contentType(
                                    ContentInfoUtil.findExtensionMatch(".png").getMimeType()
                            )
                            .build()
            );
            log.info("封面上传成功");
        } catch (Exception e) {
            log.error("封面上传失败");
            markVideo(VideoStatus.NETWORK_ERROR, "服务出错，请稍后重试");
            throw new RuntimeException(e);
        }
    }

    private static String getTempDir() {
        return System.getProperty("java.io.tmpdir");
    }

    private void toNextStep() {
        if (ProcessConst.MACHINE_AUDIT_ENABLED) {
            //标记视频状态
            markVideo(VideoStatus.WAIT_MACHINE_AUDIT, null);
            log.info("标记视频为待机器审核状态");

            //投送到机器审核队列
            rabbitTemplate.convertAndSend(
                    QueueEnum.VIDEO_PROCESS_MACHINE_CENSOR.getExchange(),
                    QueueEnum.VIDEO_PROCESS_MACHINE_CENSOR.getRoutingKey(),
                    videoProcessDTO
            );
            log.info("投送到机器审核队列");
        } else {
            //标记等待人工审核
            if (ProcessConst.MANUAL_AUDIT_ENABLED) {
                markVideo(VideoStatus.WAIT_MANUAL_AUDIT, null);
            } else {
                markVideo(VideoStatus.NORMAL, null);
            }
            log.info("等待人工审核");
        }
    }

    private void markVideo(VideoStatus status, String reason) {
        videoService.updateById(
                Video.builder()
                        .id(videoId)
                        .status(status.getStatus())
                        .reason(reason)
                        .build()
        );
    }

    private void insertVideoMetaData(VideoInfo videoInfo) {
        log.info("新增视频元数据");
        String quality = videoInfo.getImageHeight() + "x" + videoInfo.getFrameRate();
        videoMetadataService.save(
                VideoMetadata.builder()
                        .videoId(videoId)
                        .qualities("raw")
                        .duration(videoInfo.getDuration())
                        .build()
        );
    }

    private void uploadProcessedVideo(String outputVideoFilename) {
        try {
            minIOUtils.getClient().uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(ObjectName.UPLOAD_RAW.getBucket().getBucketName())
                            .object(ObjectName.UPLOAD_RAW.format(videoId))
                            .filename(outputVideoFilename)
                            .contentType(
                                    ContentInfoUtil.findExtensionMatch(".mp4").getMimeType()
                            )
                            .build()
            );
            log.info("转码后视频上传成功");
        } catch (Exception e) {
            log.error("转码后视频上传失败");
            markVideo(VideoStatus.NETWORK_ERROR, "服务出错，请稍后重试");
            throw new RuntimeException(e);
        }
    }

    private VideoInfo processTempVideo(String inputVideoFilename, String outputVideoFilename) {
        log.info("开始转码");
        VideoInfo videoInfo = null;
        try {
            videoInfo = FfmpegUtils.transform(
                    InputVideoOptions.builder()
                            .filename(inputVideoFilename)
                            .build(),
                    OutputVideoOptions.builder()
                            .filename(outputVideoFilename)
                            .videoCodec(avcodec.AV_CODEC_ID_H264)
                            .audioCodec(avcodec.AV_CODEC_ID_AAC)
                            .build(),
                    FrameHandlerOptions.builder()
                            .first(frame -> {
                                if (!ProcessConst.USE_FIRST_FRAME_AS_COVER) return;
                                try (Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();
                                     FileOutputStream outputStream = new FileOutputStream(getCoverPath());
                                ) {
                                    BufferedImage bi = java2DFrameConverter.convert(frame);
                                    ImageIO.write(bi, "png", outputStream);
                                    log.info("封面截取成功");
                                } catch (IOException e) {
                                    //截取图片失败，不重试
                                    log.error("封面截取失败");
                                    throw new AmqpRejectAndDontRequeueException(e);
                                }
                            })
                            .build()
            );
            log.info("转码成功");
            return videoInfo;
        } catch (Exception e) {
            //转码失败，是程序问题或视频没必要再尝试
            log.error("转码失败");
            markVideo(VideoStatus.TRANSCODING_FAIL, "转码失败，请重新上传正确的视频格式");
            return null;
        }
    }

    private static String getCoverPath() {
        return Paths.get(getTempDir(), "cover.png").toString();
    }

    private void downloadTempVideo(String fileMd5, String inputVideoFilename) {
        log.info("下载视频到本地");
        try {
            minIOUtils.getClient().downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(ObjectName.TEMP.getBucket().getBucketName())
                            .object(ObjectName.TEMP.format(fileMd5))
                            .overwrite(true)
                            .filename(inputVideoFilename)
                            .build()
            );
            log.info("下载成功");
        } catch (Exception e) {
            log.info("下载失败");
            markVideo(VideoStatus.NETWORK_ERROR, "服务出错，请稍后重试");
            throw new RuntimeException("下载视频失败");
        }
    }
}
